﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using LiveResults.Model;

namespace LiveResults
{
    public class OEParser : BaseParser
    {
       
        public static char[] SplitChars = new char[] { ';', '\t' };
        public OEParser()
        {
        }

        public void AnalyzeOECSVFile(string filename)
        {
            AnalyzeOECSVFile(filename, false);
        }
        public void AnalyzeOECSVFile(string filename,bool useExtraFields)
        {
            System.IO.StreamReader sr =null;
            try
            {
                /*Since OE locks the file while writing... try 10 times to read it.... */
                for (int i = 0; i < 30; i++)
                {
                    try
                    {
                        sr = new System.IO.StreamReader(filename, Encoding.Default);
                        break;
                    }
                    catch (Exception ee)
                    {
                        System.Diagnostics.Debug.WriteLine(ee.Message);
                        System.Threading.Thread.Sleep(50);
                    }
                }
                
                if (sr == null)
                    throw new ApplicationException("Could not open input file, copy error?");

                string header = sr.ReadLine();
                string[] fields = header.Split(SplitChars);

                /*Detect OE format*/
                int fldID, fldSI, fldFName, fldEName, fldClub, fldClass, fldStart, fldTime, fldStatus, fldFirstPost, fldText1, fldText2, fldText3;
                fldID = Array.IndexOf(fields, "Stno");
                if (fldID == -1)
                    fldID = Array.IndexOf(fields, "Startnr");

                fldSI = Array.IndexOf(fields, "SI card");

                if (fldSI == -1)
                    fldSI = Array.IndexOf(fields, "Chip");

                if (fldSI == -1)
                    fldSI = Array.IndexOf(fields, "Bricka");

                fldFName = Array.IndexOf(fields, "First name");
                if (fldFName == -1)
                    fldFName = Array.IndexOf(fields, "Förnamn");
                fldEName = Array.IndexOf(fields, "Surname");
                if (fldEName == -1)
                    fldEName = Array.IndexOf(fields, "Efternamn");

                fldClub = Array.IndexOf(fields, "City");
                if (fldClub == -1)
                    fldClub = Array.IndexOf(fields, "Ort");
                fldClass = Array.IndexOf(fields, "Long");
                if (fldClass == -1)
                    fldClass = Array.IndexOf(fields, "Lång");

                fldStart = Array.IndexOf(fields, "Start");

                fldTime = Array.IndexOf(fields, "Time");
                if (fldTime == -1)
                    fldTime = Array.IndexOf(fields,"Tid");

                fldStatus = Array.IndexOf(fields, "Wertung");
                if (fldStatus == -1)
                    fldStatus = Array.IndexOf(fields, "Status");

                fldFirstPost = Array.IndexOf(fields, "No1");

                fldText1 = Array.IndexOf(fields, "Text1");
                fldText2 = Array.IndexOf(fields, "Text2");
                fldText3 = Array.IndexOf(fields, "Text3");




                if (fldID == -1 || fldSI == -1 || fldFName == -1 || fldEName == -1 || fldClub == -1 || fldClass == -1
                    || fldStart == -1 || fldTime == -1)
                    throw new System.IO.IOException("Not OE-formatted file!");

                string tmp;
                while ((tmp = sr.ReadLine()) != null)
                {
                    string[] parts = tmp.Split(SplitChars);

                    /* check so that the line is not incomplete*/
                    int id = Convert.ToInt32(parts[fldID]);                    
                    int si = 0;
                    try
                    {
                        si = Convert.ToInt32(parts[fldSI]);
                    }
                    catch
                    { }
                    string firstName = parts[fldFName].Trim('\"');
                    string lastName = parts[fldEName].Trim('\"');
                    string name = firstName + " " + lastName;
                    string club = parts[fldClub].Trim('\"');
                    string Class = parts[fldClass].Trim('\"');
                    int start = strTimeToInt(parts[fldStart]);
                    int time = strTimeToInt(parts[fldTime]);

                    if (useExtraFields && fldText1 > 0 && fldText2 > 0 && fldText3 > 0)
                    {
                        name += "/" + parts[fldText1].Trim('\"');
                        club = parts[fldText3].Trim('\"');
                        if (parts[fldText2].Trim('\"') != club)
                        {
                            club += "/" + parts[fldText2].Trim('\"');
                        }

                    }

                    RunnerStatus status = RunnerStatus.OK;
                    try
                    {
                        status = (RunnerStatus)Convert.ToInt32(parts[fldStatus]);
                    }
                    catch
                    {
                    }

                    List<ResultStruct> splittimes = new List<ResultStruct>();
                    /*parse splittimes*/
                    List<int> codes = new List<int>();
                    if (fldFirstPost >= 0)
                    {
                        for (int i = fldFirstPost; i < parts.Length - 4; i++)
                        {
                            if (parts[i + 1].Length == 0
                                || parts[i + 2].Length == 0)
                            {
                                i += 3;
                                continue;
                            }
                            ResultStruct s = new ResultStruct();
                            try
                            {
                                //s.ControlNo = Convert.ToInt32(parts[i]);
                                i++;
                                s.ControlCode = Convert.ToInt32(parts[i]);

                                if (s.ControlCode == 999)
                                {                                    
                                    i++;
                                    if (time == -1 && status == 0)
                                        time = strTimeToInt(parts[i]);

                                    i++;
                                }
                                else
                                {
                                    s.ControlCode += 1000;
                                    while (codes.Contains(s.ControlCode))
                                    {
                                        s.ControlCode += 1000;
                                    }
                                    codes.Add(s.ControlCode);

                                    i++;
                                    s.Time = strTimeToInt(parts[i]);
                                    i++;
                                    s.Place = 0;
                                    try
                                    {
                                        s.Place = Convert.ToInt32(parts[i]);
                                    }
                                    catch
                                    { }

                                    splittimes.Add(s);
                                }
                            }
                            catch
                            {
                            }

                        }
                    }
                    FireOnResult(id, si, firstName, lastName, club, Class, start, time, status, splittimes);
                }
            }
            catch (Exception ee)
            {
                FireLogMsg("ERROR in OEParser: " + ee.Message);
            }
            finally
            {
                if (sr != null)
                    sr.Close();
            }
            
        }

        public Dictionary<string,EventClass> ParseIOFXmlFile(string filename)
        {
            bool processed = false;
            Dictionary<string, EventClass> ret = new Dictionary<string, EventClass>();
            for (int i = 0; i < 10; i++)
            {
                try
                {
                    StreamReader sr = null;
                    if (!File.Exists(filename))
                    {
                        return null;
                    }
                    else
                    {
                        sr = new StreamReader(filename, Encoding.Default);
                    }
                    string tmp = sr.ReadToEnd();
                    sr.Close();
                    File.Delete(filename);
                    tmp = tmp.Replace("<!DOCTYPE ResultList SYSTEM \"IOFdata.dtd\">", "");
                    processed = true;
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(tmp);


                    foreach (XmlNode classNode in xmlDoc.GetElementsByTagName("ClassResult"))
                    {
                        XmlNode classNameNode = classNode.SelectSingleNode("ClassShortName");
                        string className = classNameNode.InnerText;

                        EventClass eventClass = null;
                        if (ret.ContainsKey(className))
                        {
                            eventClass = ret[className];
                        }
                        else
                        {
                            eventClass = new EventClass();
                            ret.Add(className, eventClass);
                            eventClass.ClassName = className;
                        }

                        foreach (XmlNode personNode in classNode.SelectNodes("PersonResult"))
                        {
                            XmlNode personNameNode = personNode.SelectSingleNode("Person/PersonName");
                            string familyname = personNameNode.SelectSingleNode("Family").InnerText;
                            string givenname = personNameNode.SelectSingleNode("Given").InnerText;
                            string id = personNode.SelectSingleNode("Person/PersonId").InnerText;
                            long pid = 0;
                            if (id.Trim().Length > 0)
                            {
                                pid = Convert.ToInt64(id);
                            }
                            string club = personNode.SelectSingleNode("Club/ShortName").InnerText;
                            string status = personNode.SelectSingleNode("Result/CompetitorStatus").Attributes["value"].Value;
                            string time = personNode.SelectSingleNode("Result/Time").InnerText;
                            string si = personNode.SelectSingleNode("Result/CCard/CCardId").InnerText;
                            int iSi;
                            if (!Int32.TryParse(si, out iSi))
                            {
                                //NO SICARD!
                                FireLogMsg("No SICard for Runner: " + familyname + " " + givenname);
                            }
                            int dbid = 0;
                            if (pid < Int32.MaxValue && pid > 0)
                            {
                                dbid = (int)pid;
                            }
                            else if (iSi > 0)
                            {
                                dbid = -1 * iSi;
                            }
                            else
                            {
                                FireLogMsg("Cant generate DBID for runner: " + givenname + " " + familyname);
                            }
                            int itime = -1;
                            itime = ParseTime(time);

                            RunnerStatus istatus = RunnerStatus.STARTED;

                            switch (status)
                            {
                                case "MisPunch":
                                    istatus = RunnerStatus.MISSING_PUNCH;
                                    break;

                                case "Disqualified":
                                    istatus = RunnerStatus.DISQUALIFIED;
                                    break;
                                case "DidNotFinish":
                                    istatus = RunnerStatus.MISSING_PUNCH;
                                    itime = -2;
                                    break;
                                case "DidNotStart":
                                    istatus = RunnerStatus.DID_NOT_START;
                                    itime = -3;
                                    break;
                                case "Overtime":
                                    istatus = RunnerStatus.OVERTIME;
                                    break;
                                case "OK":
                                    istatus = RunnerStatus.OK;
                                    break;
                            }

                            Runner r = new Runner();
                            r.ClassName = className;
                            r.ClubName = club;
                            r.FirstName = givenname;
                            r.ID = dbid;
                            r.LastName = familyname;
                            r.Status = istatus;
                            r.Time = itime;

                            eventClass.Runners.Add(dbid, r);


                            List<int> lsplitCodes = new List<int>();
                            List<int> lsplitTimes = new List<int>();

                            XmlNodeList splittimes = personNode.SelectNodes("Result/SplitTime");
                            List<ResultStruct> splits = new List<ResultStruct>();
                            if (splittimes != null)
                            {
                                foreach (XmlNode splitNode in splittimes)
                                {
                                    XmlNode splitcode = splitNode.SelectSingleNode("ControlCode");
                                    XmlNode splittime = splitNode.SelectSingleNode("Time");
                                    int i_splitcode;
                                    string s_splittime = splittime.InnerText;
                                    if (int.TryParse(splitcode.InnerText, out i_splitcode) && s_splittime.Length > 0)
                                    {
                                        if (i_splitcode == 999)
                                        {
                                            if (istatus == 0 && itime == -1)
                                            {
                                                //Målstämpling
                                                itime = ParseTime(s_splittime);
                                            }
                                        }
                                        else
                                        {
                                            i_splitcode += 1000;
                                            while (lsplitCodes.Contains(i_splitcode))
                                            {
                                                i_splitcode += 1000;
                                            }

                                            int i_splittime = ParseTime(s_splittime);
                                            lsplitCodes.Add(i_splitcode);
                                            lsplitTimes.Add(i_splittime);

                                            ResultStruct rs = new ResultStruct();
                                            rs.ControlCode = i_splitcode;
                                            rs.Time = i_splittime;
                                            splits.Add(rs);
                                        }
                                    }
                                }
                            }

                            for (int c = 0; c < splits.Count; c++)
                            {
                                r.RadioTimes.Add(splits[c].ControlCode, splits[c].Time);
                            }
                        }
                    }
                }
                catch (Exception ee)
                {
                    FireLogMsg(ee.Message);
                }

                if (!processed)
                {
                    System.Threading.Thread.Sleep(1000);
                }
                else
                {
                    break;
                }
            }
            if (!processed)
            {
                FireLogMsg("Could not open " + filename + " for processing");
                return null;
            }
            else
            {
                return ret;
            }
        }

        /// <summary>
        /// Used to parse XML-time
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        private int ParseTime(string time)
        {
            int itime = -1;
            string[] timeParts = time.Split(':');
            if (timeParts.Length == 3)
            {
                //HH:MM:SS
                itime = Convert.ToInt32(timeParts[0]) * 360000 + Convert.ToInt32(timeParts[1]) * 6000 + Convert.ToInt32(timeParts[2]) * 100;
            }
            else if (timeParts.Length == 2)
            {
                //MM:SS
                itime = Convert.ToInt32(timeParts[0]) * 6000 + Convert.ToInt32(timeParts[1]) * 100;
            }
            return itime;
        }


        /// <summary>
        /// Used to parse OE-CSV time
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        private int strTimeToInt(string time)
        {
            try
            {
                /* format is: 18:15,0*/
                string[] parts = time.Split(':');
                int totalTime = 0;
                /*CHANGED TO DECIMAL*/
                totalTime += (int)(Convert.ToDecimal(parts[parts.Length - 1]) * 100);

                int mod = 6000;
                for (int i = parts.Length - 2; i >= 0; i--)
                {
                    totalTime += Int32.Parse(parts[i]) * mod;
                    mod *= 60;
                }
                return totalTime;
            }
            catch
            {
                return -1;
            }
            
        }
    }

    public struct ControlStruct
    {
        public string Name { get; set; }
        public int Code { get; set; }
    }

    public struct ResultStruct
    {
        public int ControlNo;
        public int ControlCode;
        public int Time;
        public int Place;
    }
}
